package typeCheck;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public abstract class Type {

    public String name;
    public TypeEnum symbolType;

    static Set<String> intSet = new HashSet<String>();

    static Set<String> floatSet = new HashSet<String>();

    static Set<String> stringSet = new HashSet<String>();

    static Set<String> boolSet = new HashSet<String>();

    static {
        // add int type into intSet
        intSet.add("int");
        intSet.add("int8");
        intSet.add("int16");
        intSet.add("int32");
        intSet.add("int64");
        intSet.add("uint");
        intSet.add("uint8");
        intSet.add("uint16");
        intSet.add("uint32");
        intSet.add("uint64");

        // add float and complex into floatSet
        floatSet.add("float32");
        floatSet.add("float64");
        floatSet.add("complex64");
        floatSet.add("complex128");

        // add string into stringSet
        stringSet.add("string");

        // add bool into boolSet
        boolSet.add("bool");
    }

    public Type(String name, TypeEnum typeEnum) {
        this.name = name;
        this.symbolType = typeEnum;
    }

    public static boolean comparable(Type type1, Type type2) {
        if (type1.symbolType != TypeEnum.FUNDAMENTAL) {
            return Objects.equals(type1.name, type2.name);
        } else if (intSet.contains(type1.name) && intSet.contains(type2.name)) {
            return true;
        } else if (floatSet.contains(type1.name) && floatSet.contains(type2.name)) {
            return true;
        } else if (stringSet.contains(type1.name) && stringSet.contains(type2.name)) {
            return true;
        }
        return boolSet.contains(type1.name) && boolSet.contains(type2.name);
    }

    public static Type typeClone(String name, Type srcType){
        switch (srcType.symbolType) {
            case FUNCTION: {
                FunctionType newFunc = new FunctionType(name);
                FunctionType oldFunc = (FunctionType) srcType;
                for (String paramName : oldFunc.getParameters().keySet()) {
                    newFunc.addParameter(paramName, oldFunc.getParameters().get(paramName));
                }
                for (String resultName : oldFunc.getResults().keySet()) {
                    newFunc.addParameter(resultName, oldFunc.getParameters().get(resultName));
                }
                return newFunc;
            }
            case FUNDAMENTAL: {
                FundamentalType newType = new FundamentalType(name);
                if (Type.intSet.contains(srcType.name))
                    Type.intSet.add(name);
                if (Type.floatSet.contains(srcType.name))
                    Type.floatSet.add(name);
                if (Type.stringSet.contains(srcType.name))
                    Type.stringSet.add(name);
                if (Type.boolSet.contains(srcType.name))
                    Type.boolSet.add(name);
                return newType;
            }
            case POINTER: {
                PointerType oldPointer = (PointerType) srcType;
                return new PointerType(name, oldPointer.getPointedType());
            }
            case ARRAY: {
                ArrayType oldArray = (ArrayType) srcType;
                return new ArrayType(name, oldArray.getElementType());
            }
            case STRUCT: {
                StructType oldStruct = (StructType) srcType;
                StructType newStruct = new StructType(name);

                for (String attributeName : oldStruct.getAttributes().keySet()) {
                    newStruct.addAttribute(oldStruct.getAttributes().get(attributeName));
                }

                for (String methodName : oldStruct.getMethods().keySet()) {
                    newStruct.addMethod(oldStruct.getMethods().get(methodName));
                }

                newStruct.setFiledNum(oldStruct.getFiledNum());
                return newStruct;
            }
            case MAP: {
                MapType oldMap = (MapType) srcType;
                return new MapType(name, oldMap.getKeyType(), oldMap.getValueType());
            }
            case INTERFACE: {
                InterfaceType oldInterface = (InterfaceType) srcType;
                InterfaceType newInterface = new InterfaceType(name);

                for (String methodName : oldInterface.getMethods().keySet()) {
                    newInterface.addMethod(oldInterface.getMethods().get(methodName));
                }

                return newInterface;
            }
            case CHANNEL: {
                ChannelType oldChannel = (ChannelType) srcType;
                return new ChannelType(name, oldChannel.getChannelTypes(), oldChannel.getElementType());
            }
            default: break;
        }
        return null;
    }

}
